package com.wits.media;

import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;

import com.wits.av.MFormat;
import com.wits.av.MPacket;
import com.wits.av.MediaSink;
import com.wits.av.PsCaster;
import com.wits.av.PsCasterListener;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * Created by zhengboyuan on 2018-03-12.
 */

public class CPsCamera implements PsCamera, MediaSink {

    private static final String TAG = "jni PsCamera";

    protected OsdOverlayMixer overlayMixer = new COsdOverlayMixer();
    protected MFormat   format;
    protected PsCaster  psCaster;
    protected CameraEncoder cameraEncoder = new CameraEncoder();
    protected AudioEncoder  audioEncoder = new AudioEncoder();

    protected int protocol = PsCaster.PROTOCOL_RTP_UDP;
    protected int payload = 96;
    protected int ssrc = 1;
    protected boolean esMode = false;

    protected boolean started = false;

    protected BlockingQueue<MPacket> packetQueue = new LinkedBlockingDeque<>();
    protected Thread casterThread;

    protected MediaSink mediaSink;


    public CPsCamera() {
        psCaster = new PsCaster();
    }



    @Override
    public boolean setFormat(MFormat fmt) {
        if (fmt == null) {
            return false;
        }

        format = fmt;

        format.clockRate = 1000000;
        format.audioRate = 1000000;

        if (fmt.width <= 0 || fmt.height <= 0) {
            fmt.width = 640;
            fmt.height = 480;
        }

        return true;
    }

    @Override
    public MFormat getFormat() {
        return format;
    }

    @Override
    public boolean setOsd(int index, OsdOverlay overlay) {
        return overlayMixer.setOverlay(index, overlay);
    }

    @Override
    public void clearOsd() {
        overlayMixer.clearOverlay();
    }

    @Override
    public void setProtocol(int protocol, int payload, int ssrc, boolean esMode) {
        this.protocol = protocol;
        this.payload = payload;
        this.ssrc = ssrc;
        this.esMode = esMode;
    }

    @Override
    public void setDestination(String ip, int port) {
        psCaster.removeAllTarget();
        psCaster.addTarget(ip, port);
    }

    @Override
    public boolean open(int cameraId, SurfaceHolder surfaceHolder) {
        Log.i(TAG, "CPsCamera.open. thread: " + Thread.currentThread().getId());

        cameraEncoder.setCamera(cameraId);
        cameraEncoder.setSurfaceHolder(surfaceHolder);
        cameraEncoder.setSink(this);
        cameraEncoder.setOverlayMixer(overlayMixer);

        if (!cameraEncoder.open(format)) {
            return false;
        }

        if (hasAudio()) {
            audioEncoder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
            audioEncoder.setSink(this);

            if (!audioEncoder.open(format)) {
                Log.w(TAG, "failed to open audio encoder");
                // ignore audio
                format.audioCodec = 0;
            }
        }

        boolean ret = psCaster.open(protocol, 0, format, payload, ssrc, esMode);

        Log.i(TAG, "CPsCamera.open end");

        return ret;
    }

    @Override
    public void close() {
        Log.i(TAG, "CPsCamera.close. thread: " + Thread.currentThread().getId());

        cameraEncoder.close();
        audioEncoder.close();

        synchronized (this) {
            psCaster.close();
        }

        sleep(20);

        Log.i(TAG, "CPsCamera.close end");
    }

    @Override
    public boolean start() {
        Log.i(TAG, "CPsCamera.start");

        if (!cameraEncoder.isOpen() && !audioEncoder.isOpen()) {
            Log.w(TAG, "encoder is not open");
            return false;
        }

        if (!psCaster.isOpen()) {
            Log.w(TAG, "pscaster is not open");
            return false;
        }

        boolean ret = cameraEncoder.start();
        audioEncoder.start();

        setStarted(true);

        if (casterThread == null) {
            casterThread = new Thread(new CasterThread());
            casterThread.start();
        }

        Log.i(TAG, "CPsCamera.start end");
        return ret;
    }

    @Override
    public void stop() {
        Log.i(TAG, "CPsCamera.stop");

        setStarted(false);

        if (casterThread != null) {
            casterThread.interrupt();

            try {
                casterThread.join();
            } catch (Throwable t) {
                //
            }

            casterThread = null;
        }

        audioEncoder.stop();
        cameraEncoder.stop();

        Log.i(TAG, "CPsCamera.stop end");
    }

    @Override
    public boolean isStarted() {
        return cameraEncoder.isOpen() && psCaster.isOpen() && started;
    }


    @Override
    public void setRotation(int rotation) {
        cameraEncoder.setRotation(rotation);
    }

    @Override
    public void setDisplayOrientation(int degree) {
        cameraEncoder.setDisplayOrientation(degree);
    }

    @Override
    public void setMediaSink(MediaSink sink) {
        this.mediaSink = sink;

        if (mediaSink != null) {
            mediaSink.onMediaFormat(format);
        }
    }



    @Override
    public void onMediaFormat(MFormat fmt) {
        if (mediaSink != null) {
            mediaSink.onMediaFormat(fmt);
        }
    }

    @Override
    public void onMediaPacket(MPacket pkt) {

        synchronized (this) {
            if (!started) {
                Log.w(TAG, "started is false");
                return;
            }
        }

        try {
            packetQueue.put(pkt);
        } catch (Throwable t) {
            t.printStackTrace();
        }

//            if (pkt.type == MediaType.MEDIA_TYPE_VIDEO) {
//                dumpPacket(pkt);
//            }

    }

    @Override
    public void onMediaEvent(int event) {

        if (mediaSink != null) {
            mediaSink.onMediaEvent(event);
        }
    }

    @Override
    public void setCasterEventCallback(PsCasterListener listener) {
        psCaster.setCasterEventCallback(listener);
    }


    private boolean hasAudio() {
        return (format.audioCodec > 0) && (format.channels) > 0 && (format.sampleRate > 0);
    }

    private void dumpPacket(MPacket pkt) {
        String root = Environment.getExternalStorageDirectory().toString();
        File myDir = new File(root + "/Download");
        myDir.mkdirs();

        String filename = "pkt.text";
        File file = new File(myDir, filename);

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, true))) {
            writer.write(Integer.toString(pkt.type));
            writer.write(',');
            writer.write(Integer.toString(pkt.data.length));
            writer.write(',');
            writer.write(Integer.toString(pkt.duration));
            writer.write(',');
            writer.write(Long.toString(pkt.pts));
            writer.write(',');
            writer.write(Integer.toString(pkt.flags));
            writer.write('\n');
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }


    private void setStarted(boolean started) {
        synchronized (this) {
            this.started = started;
        }
    }

    private boolean getStarted() {
        synchronized (this) {
            return started;
        }
    }

    private void sleep(int ms) {
        try {
            Thread.sleep(ms);
        } catch (Throwable t) {
            // pass
        }
    }

    private void firePacket(MPacket pkt) {
        psCaster.write(pkt);

        if (mediaSink != null) {
            mediaSink.onMediaPacket(pkt);
        }
    }


    class CasterThread implements Runnable {

        @Override
        public void run() {
            while (started) {
                try {
                    MPacket pkt = packetQueue.take();
                    firePacket(pkt);
                } catch (Throwable t) {
                    //
                }
            }
        }
    }


}
